package rbfnn;

import java.util.List;

public class RBF {
	public List<Training> Trainings;
	public double[] Center;
	public double[][] BasFunc;
	public double[] Weight;
	public double MaxDistance;
	//public double[] Input;
	
	public RBF(List<Training> _Trainings, double[] _Center)
	{
		this.Trainings = _Trainings;
		this.Center = _Center;
		this.MaxDistance = CalcDMax();
	}
	
	/*
	 * getBasisFunction() is a method to get Basis Function value from every Training data's 
	 * which then used to calculate the optimum weight using least square algorithm
	 * */
	public double[][] getBasFunc()
	{
		return BasFunc;
	}
	
	public void setWeight(double[] _Weight)
	{
		this.Weight = _Weight;
	}
	
	/*
	 * CalculateOutput() is a method to calculate output from testing data's
	 * */
	public double CalculateOutput(double[] Input, int m)
	{
		int c = 0, d = 0;
		double Sum = 0.0, exp = 0.0;
		double FinalOutput = 0.0;
		int SplitInput = this.Trainings.get(0).Inputs.size() + 1;
		///System.out.println("Euclidean Distance: ");
		for(int j = 0; j < Center.length; j++)
		{
			if( (j+1) % SplitInput != 0)
			{
				Sum = Sum + Math.pow(Input[c] - Center[j], 2);
				System.out.println("||" + Input[c] + " - " + Math.round( Center[j] * 10000.0 ) / 10000.0 + "||");
				c++;
			}
			else
			{
				FinalOutput = FinalOutput + Math.exp(-(m / MaxDistance) * Sum) * Weight[d];
				//System.out.println(Sum);
				exp = -1*(m / MaxDistance) * Sum;
				//System.out.println("Center [" + (d+1) + "] " + "EXP: " + exp);
				System.out.println("Output Center ["  + (d+1) + "] " + "= " + " EXP( -(m / Maxdistance^2) x || x-ci ||)" +" x Weight" );
				System.out.println("Output Center [" + (d+1) + "] " + "= "+ Math.round( Math.exp(exp) * 10000.0 ) / 10000.0 + " x " + Math.round( Weight[d] * 10000.0 ) / 10000.0 + " = " + Math.exp(-((m / MaxDistance) * Sum)) * Weight[d]);
				System.out.println();
				Sum = 0.0;
				c = 0;
				d++;
			}
		}
		return FinalOutput;
	}
	
	/*
	 * Training() is a method to calculate basis function exp(-||xk - vi||^2 / o^2) 
	 * for every training datas, m is the number of clusters
	 * */
	public void Training(int m) 
	{
		int c = 0, d = 0;
		double Sum = 0.0;
		BasFunc = new double[Trainings.size()][m];
		int SplitInput = this.Trainings.get(0).Inputs.size() + 1;
		for(int i = 0; i < Trainings.size(); i++)
		{
			for(int j = 0; j < Center.length; j++)
			{
				if( (j+1) % SplitInput != 0)
				{
					Sum = Sum + Math.pow(Trainings.get(i).Inputs.get(c) - Center[j], 2);
					// System.out.println(Trainings.get(i).Inputs.get(c) + " - " + Center[j]);
					c++;
				}
				else
				{
					BasFunc[i][d] = Math.exp(-(m / MaxDistance) * Sum); 
				    System.out.print(BasFunc[i][d] + " ");
					Sum = 0.0;
					c = 0;
					d++;
				}
			}
			d = 0;
		    System.out.println();
		}
	}
	
	/*
	 * CalcDMax() is a method to calculate maximum distance between all center clusters
	 * */
	
	public double CalcDMax()
	{
		int splitInput = this.Trainings.get(0).Inputs.size() + 1;
		double Sum = 0.0;
		double MaxDistance = 0.0;
		for(int i = 0; i < Center.length; i += splitInput)
		{
			for(int j = i + splitInput; j < Center.length; j += splitInput)
			{
				for(int k = 0; k < splitInput - 1; k++)
				{
					// System.out.println((i + k) + " - " + (j + k));
					Sum = Sum + Math.pow((Center[i + k] - Center[j + k]), 2);
				}
				
				if(Math.sqrt(Sum) > MaxDistance)
				{
					MaxDistance = Math.sqrt(Sum);
				}
				// System.out.println("Distance: " + Math.sqrt(Sum));
				// System.out.println("Max: " + MaxDistance);
				Sum = 0.0;
			}
			// System.out.println();
		}
		return Math.pow(MaxDistance, 2);
	}
	
	public void calculateRMSE(int _NumOfTrain, int m, double[] _Output)
	{
		double Sum = 0.0, RMSE = 0.0;
		for(int i = 0; i < Trainings.size(); i++)
		{
			for(int j = 0; j < m; j++)
			{
				Sum = Sum + (BasFunc[i][j] * Weight[j]);
			}
			RMSE += Math.pow((Sum - _Output[i]), 2);
			//System.out.println("RMSE: " + RMSE);
			//System.out.println(Math.round( Sum * 10000.0 ) / 10000.0 + " " + _Output[i]);
			Sum = 0.0;
		}
		System.out.println("RMSE: " + Math.round( Math.sqrt(RMSE / _NumOfTrain) * 10000.0 ) / 10000.0);
	}
}
